#ifndef __DYNAMIC_STRING_H__
#define __DYNAMIC_STRING_H__
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "dynamic_array.h"

// It is an equivalent of shift command from bash. It basically pops a command line
// argument from the beginning.
char *dyn_str_shift_args(int *argc, char ***argv);

// --- Example generic implementation
// typedef struct dyn_array {
//     void* items;
//     u64 length;
//     u64 capacity;
// } dyn_array_t;
//

typedef struct String_Builder {
    char *items;
    u64 length;
    u64 capacity;
} String_Builder;

// Long form append to a SB
#define sb_append(sb, buf, size) dyn_array_append_many((sb), (buf), (size))

// Append a null terminated C "string" on a String_Builder
#define sb_append_cstr(sb, cstr)  \
    do {                              \
        const char *sb_s = (cstr);       \
        size_t sb_n = strlen(sb_s);         \
        dyn_array_append_many((sb), sb_s, sb_n); \
    } while (0)

// Add a null terminator to a SB; makes SB.items a "C String"
#define sb_append_null(sb) dyn_array_append_many((sb), "", 1)

bool sb_read_entire_file(const char *path, String_Builder *sb);

typedef struct String_View {
    u64 length;
    const char *data;
} String_View;

const char *temp_sv_to_cstr(String_View sv);

String_View sv_chop_by_delim(String_View *sv, char delim);
String_View sv_trim(String_View sv);
bool sv_eq(String_View a, String_View b);
String_View sv_from_cstr(const char *cstr);
String_View sv_from_parts(const char *data, size_t count);

// printf macros for String_View
// USAGE:
//   String_View name = ...;
//   printf("Name: "SV_Fmt"\n", SV_Arg(name));
#ifndef SV_Fmt
#define SV_Fmt "%.*s"
#endif // SV_Fmt
#ifndef SV_Arg
#define SV_Arg(sv) (int) (sv).length, (sv).data
#endif // SV_Arg

typedef struct String_Builder_Array {
    String_Builder *items;
    u64 length;
    u64 capacity;
} String_Builder_Array;

typedef struct String_Builder_Array_2D {
    String_Builder_Array *items;
    u64 length;
    u64 capacity;
} String_Builder_Array_2D;

#define sb_free(sb) reset_free((sb));
#define sba_free(sba) \
    do { \
        for(u64 sba_i=0; sba_i<(sba).length; ++sba_i) { \
            sb_free((sba).items[sba_i]); \
        } \
        (sba).length = 0; \
    } while(0)

#define sba_reset(sba) \
    do { \
        for(u64 sba_i=0; sba_i<(sba).length; ++sba_i) { \
            String_Builder *sbar_sb = &(sba).items[sba_i]; \
            memset(sbar_sb->items, 0, sbar_sb->length); \
            sbar_sb->length = 0; \
        } \
        (sba).length = 0; \
    } while(0)

#define sba2d_reset(sba2d) \
    do { \
        for(u64 sba2d_i=0; sba2d_i<(sba2d).length; ++sba2d_i) { \
            sba_reset((sba2d).items[sba2d_i]); \
        } \
        (sba2d).length = 0; \
    } while(0)

#define sba2d_free(sba2d) \
    do { \
        for(u64 sba2d_i=0; sba2d_i<(sba2d).length; ++sba2d_i) { \
            sba_free((sba2d).items[sba2d_i]); \
            reset_free((sba2d).items[sba2d_i]); \
        } \
        (sba2d).length = 0; \
    } while(0)

#define sba_append(sba, sb) \
    do { \
        String_Builder sbaa = {0}; \
        dyn_array_append_many(&sbaa, (sb)->items, (sb)->length); \
        dyn_array_append((sba), sbaa); \
    } while(0)

#define sba_append_cstr(sba, str) \
    do { \
        String_Builder sbac = {0}; \
        sb_append_cstr(&sbac, (str)); \
        sb_append_null(&sbac); \
        sba_append((sba), &sbac); \
        sb_free(sbac); \
    } while(0)

#define sba2d_append(sba2d, sba) \
    do { \
        String_Builder_Array sba2da = {0}; \
        for(u64 sba2da_i=0; sba2da_i<(sba)->length; ++sba2da_i) { \
            String_Builder *sba2da_item = &(sba)->items[sba2da_i]; \
            sba_append(&sba2da, sba2da_item); \
        } \
        dyn_array_append((sba2d), sba2da); \
    } while(0)

#define sba2d_copy(source, target) \
    do { \
        for(u64 sba2d_i=0; sba2d_i<(source)->length; ++sba2d_i) { \
            sba2d_append((target), (source)->items[sba2d_i]); \
        } \
    } while(0)

#define sba2d_transpose(source, target) \
    do { \
        u64 source_rows = (source)->length; \
        u64 source_cols = (source)->items->length; \
        for(u64 sba2d_col=0; sba2d_col<source_cols; ++sba2d_col) { \
            String_Builder_Array target_col = {0}; \
            for(u64 sba2d_row=0; sba2d_row<source_rows; ++sba2d_row) { \
                sba_append(&target_col, &(source)->items[sba2d_row].items[sba2d_col]); \
            } \
            sba2d_append((target), &target_col); \
            sba_free(target_col); \
            reset_free(target_col); \
        } \
    } while(0)

#ifdef DYN_STR_IMPLEMENTATION 
#define return_defer(value) do { result = (value); goto defer; } while(0)

bool sb_read_entire_file(const char *path, String_Builder *sb) {
    bool result = true;

    FILE *f = fopen(path, "rb");
    if (f == NULL)                 return_defer(false);
    if (fseek(f, 0, SEEK_END) < 0) return_defer(false);
    long m = ftell(f);
    if (m < 0)                     return_defer(false);
    if (fseek(f, 0, SEEK_SET) < 0) return_defer(false);

    size_t new_count = sb->length + m;
    if (new_count > sb->capacity) {
        sb->items = realloc(sb->items, new_count);
        assert(sb->items != NULL && "Buy more RAM lool!!");
        sb->capacity = new_count;
    }

    fread(sb->items + sb->length, m, 1, f);
    if (ferror(f)) {
        // TODO: Afaik, ferror does not set errno. So the error reporting in defer is not correct in this case.
        return_defer(false);
    }
    sb->length = new_count;

defer:
    if (!result) fprintf(stderr, "Could not read file %s: %s", path, strerror(errno));
    if (f) fclose(f);
    return result;
}

String_View sv_chop_by_delim(String_View *sv, char delim) {
    size_t i = 0;
    while (i < sv->length && sv->data[i] != delim) {
        i += 1;
    }

    String_View result = sv_from_parts(sv->data, i);

    if (i < sv->length) {
        sv->length -= i + 1;
        sv->data  += i + 1;
    } else {
        sv->length -= i;
        sv->data  += i;
    }

    return result;
}

String_View sv_from_parts(const char *data, size_t length) {
    String_View sv;
    sv.length = length;
    sv.data = data;
    return sv;
}

String_View sv_trim_left(String_View sv)
{
    size_t i = 0;
    while (i < sv.length && isspace(sv.data[i])) {
        i += 1;
    }

    return sv_from_parts(sv.data + i, sv.length - i);
}

String_View sv_trim_right(String_View sv) {
    size_t i = 0;
    while (i < sv.length && isspace(sv.data[sv.length - 1 - i])) {
        i += 1;
    }

    return sv_from_parts(sv.data, sv.length - i);
}

String_View sv_trim(String_View sv) {
    return sv_trim_right(sv_trim_left(sv));
}

String_View sv_from_cstr(const char *cstr) {
    return sv_from_parts(cstr, strlen(cstr));
}

bool sv_eq(String_View a, String_View b) {
    if (a.length != b.length) {
        return false;
    } else {
        return memcmp(a.data, b.data, a.length) == 0;
    }
}

char *dyn_str_shift_args(int *argc, char ***argv) {
    assert(*argc > 0);
    char *result = **argv;
    (*argv) += 1;
    (*argc) -= 1;
    return result;
}
#endif // DYN_STR_IMPLEMENTATION

#endif // __DYNAMIC_STRING_H__